package br.feevale.escalonador.view;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;

import br.feevale.escalonador.domain.Processo;
import br.feevale.escalonador.util.enums.StatusProcessoEnum;
import br.feevale.escalonador.util.exceptions.ConfiguracaoException;
import br.feevale.escalonador.view.tablemodel.ProcessoEsperaTableModel;
import br.feevale.escalonador.view.tablemodel.ProcessoHistoricoTableModel;
import br.feevale.escalonador.view.tablemodel.ProcessoTableModel;

@SuppressWarnings("serial")
public class EscalonadorView extends JFrame {
	
	private static Double QUANTUM = 4D;
	private static Double NRO_PROCESSOS_MINUTO = 30D;
	private static Double MIN_TEMPO_VIDA = 2D;
	private static Double MAX_TEMPO_VIDA = 6D;
	private static Double PROBABILIDADE_IO_BOUND = 50D;
	private static Double MIN_TEMPO_PROCESSAMENTO_IO = 2D;
	private static Double MIN_TEMPO_ESPERA = 5D;
	private static Double MAX_TEMPO_ESPERA = 50D;
	
	private static final String BTN_INICIAR = "Iniciar";
	private static final String BTN_FINALIZAR = "Finalizar";
	
	private JButton btnIniciar;
	private JButton btnFinalizar;
	
	private Thread threadFilaProcessos;
	private Thread threadProcessamento;
	
	private Queue<Processo> processos;
	private ProcessoTableModel processoTableModel;
	private JTable table;
	
	private LinkedList<Processo> processosHistorico;
	private ProcessoHistoricoTableModel processoHistoricoTableModel;
	private JTable tableHistorico;
	
	private Queue<Processo> processosEspera;
	private ProcessoEsperaTableModel processoEsperaTableModel;
	private JTable tableEspera;
	
	private JTextField tfQuantum;
	private JTextField tfMinTempoVida;
	private JTextField tfMaxTempoVida;
	private JTextField tfQtdProcessosMinuto;
	private JTextField tfPercentualIoBound;
	private JTextField tfMinTempoProcessamentoIoBound;
	private JTextField tfMinTempoEspera;
	private JTextField tfMaxTempoEspera;
	
	private JTextField tfIdProcesso;
	private JTextField tfNomeProcesso;
	private JTextField tfTempoVida;
	private JTextField tfTempoProcessado;
	private JTextField tfTentativa;
	private JTextField tfIoBound;
	
	private boolean processando = false;
	private boolean addOrRemoveEspera = false;
	Integer idx = 1;
	private List<Thread> threads;
	
	public EscalonadorView() {
		setSize(900, 600);
		setTitle("Escalonador de processos");
		setVisible(true);
		setResizable(false);
		setLayout(null);
		
		initLayout();
		
		setLocationRelativeTo(null);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
	}
	
	private void initLayout() {
		initConfiguracao();
		initProcessamento();
		initFilaProcessos();
		initFilaEspera();
		initFilaHistorico();
		setDefaultValues();
	}
	
	/**
	 * Define valores padrão para o bloco de configuração do simulador.
	 */
	private void setDefaultValues() {
		tfQuantum.setText(QUANTUM.toString());
		tfQtdProcessosMinuto.setText(NRO_PROCESSOS_MINUTO.toString());
		tfMinTempoVida.setText(MIN_TEMPO_VIDA.toString());
		tfMaxTempoVida.setText(MAX_TEMPO_VIDA.toString());
		tfPercentualIoBound.setText(PROBABILIDADE_IO_BOUND.toString());
		tfMinTempoProcessamentoIoBound.setText(MIN_TEMPO_PROCESSAMENTO_IO.toString());
		tfMinTempoEspera.setText(MIN_TEMPO_ESPERA.toString());
		tfMaxTempoEspera.setText(MAX_TEMPO_ESPERA.toString());
	}
	
	/**
	 * Define os componentes utilizados no bloco de processamento atual dos processos.
	 */
	private void initProcessamento() {
		JPanel pnlProcessamento = new JPanel();
		pnlProcessamento.setLayout(null);
		pnlProcessamento.setBorder(BorderFactory.createTitledBorder("Processamento Atual"));
		pnlProcessamento.setBounds(450, 10, 435, 170);
		
		JLabel lblId = new JLabel("Código:");
		lblId.setBounds(10, 20, 120, 20);
		pnlProcessamento.add(lblId);
		
		tfIdProcesso = new JTextField();
		tfIdProcesso.setBounds(130, 20, 295, 20);
		tfIdProcesso.setEditable(false);
		pnlProcessamento.add(tfIdProcesso);
		
		JLabel lblNomeProcesso = new JLabel("Nome:");
		lblNomeProcesso.setBounds(10, 50, 120, 20);
		pnlProcessamento.add(lblNomeProcesso);
		
		tfNomeProcesso = new JTextField();
		tfNomeProcesso.setBounds(130, 50, 295, 20);
		tfNomeProcesso.setEditable(false);
		pnlProcessamento.add(tfNomeProcesso);
		
		JLabel lblTempoVidaProcesso = new JLabel("Tempo de vida:");
		lblTempoVidaProcesso.setBounds(10, 80, 120, 20);
		pnlProcessamento.add(lblTempoVidaProcesso);
		
		tfTempoVida = new JTextField();
		tfTempoVida.setBounds(130, 80, 295, 20);
		tfTempoVida.setEditable(false);
		pnlProcessamento.add(tfTempoVida);
		
		JLabel lblTempoVidaProcessado = new JLabel("Tempo processado:");
		lblTempoVidaProcessado.setBounds(10, 110, 120, 20);
		pnlProcessamento.add(lblTempoVidaProcessado);
		
		tfTempoProcessado = new JTextField();
		tfTempoProcessado.setBounds(130, 110, 295, 20);
		tfTempoProcessado.setEditable(false);
		pnlProcessamento.add(tfTempoProcessado);
		
		JLabel lblIoBound = new JLabel("I/O Bound:");
		lblIoBound.setBounds(10, 140, 120, 20);
		pnlProcessamento.add(lblIoBound);
		
		tfIoBound = new JTextField();
		tfIoBound.setBounds(130, 140, 130, 20);
		tfIoBound.setEditable(false);
		pnlProcessamento.add(tfIoBound);
		
		JLabel lblTentativa = new JLabel("Tentativa:");
		lblTentativa.setBounds(270, 140, 60, 20);
		pnlProcessamento.add(lblTentativa);
		
		tfTentativa = new JTextField();
		tfTentativa.setBounds(350, 140, 75, 20);
		tfTentativa.setEditable(false);
		pnlProcessamento.add(tfTentativa);
		
		add(pnlProcessamento);
		
		repaint();
	}
	
	/**
	 * Define o tempo a ser considerado como intervalo entre a criação de novos processos.
	 */
	private Long getIntervaloProcessos() {
		return (long) ((60 / NRO_PROCESSOS_MINUTO) * 1000);
	}
	
	/**
	 * Define os componentes utilizados no bloco de configuração dos processos.
	 */
	private void initConfiguracao() {
		JPanel pnlConfiguracao = new JPanel();
		pnlConfiguracao.setLayout(null);
		pnlConfiguracao.setBorder(BorderFactory.createTitledBorder("Configuração"));
		pnlConfiguracao.setBounds(10, 10, 435, 170);
		
//		Icon iconPlay = new ImageIcon(EscalonadorView.class.getResource("../resources/icon_play.png"));
		btnIniciar = new JButton("Iniciar");
		btnIniciar.setToolTipText("Iniciar");
		btnIniciar.setMnemonic('I');
		btnIniciar.setBounds(220, 135, 100, 25);
		btnIniciar.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				ConfiguracaoException exception = validaConfiguracao();
				
				if (exception.isResultado()) {
					estadoConfiguracao(false);
					setValores();
					
					threads = new ArrayList<Thread>();
					
					threadFilaProcessos = new Thread(new Runnable() {
						
						Long intervaloNovoProcesso = getIntervaloProcessos();
						Integer idxIo = 1;
						Double mediaIo = 100 / PROBABILIDADE_IO_BOUND;
						
						@Override
						public void run() {
							
							while (true) {
								try {
									Thread.sleep(intervaloNovoProcesso);
									Processo processo = new Processo();
									processo.setId(idx);
									processo.setNome("Processo " + idx);
									processo.setTempoVida(generateRandonTime(MIN_TEMPO_VIDA, MAX_TEMPO_VIDA));
									processo.setTempoProcessado(0D);
									processo.setIoBound(defineIoBound(idxIo, mediaIo));
									processo.setTempoEspera(defineTempoIoBound());
									processo.setTentativa(1);
									processo.setStatus(StatusProcessoEnum.PRONTO);
									
									processos.add(processo);
									processoTableModel.fireTableDataChanged();
									
									if (processo.isIoBound()) {
										idxIo = 1;
									} else {
										idxIo++;
									}
								} catch (InterruptedException e) {
									e.printStackTrace();
								}
								
								idx++;
							}
						}
					});
					
					threadFilaProcessos.start();
					
					enableButtons(BTN_INICIAR);
				} else {
					JOptionPane.showMessageDialog(EscalonadorView.this, exception.getMensagem());
				}
			}

		});
		
		pnlConfiguracao.add(btnIniciar);
		
//		Icon iconStop = new ImageIcon(EscalonadorView.class.getResource("../resources/icon_stop.png"));
		btnFinalizar = new JButton("Finalizar");
		btnFinalizar.setToolTipText("Finalizar");
		btnFinalizar.setMnemonic('F');
		btnFinalizar.setBounds(325, 135, 100, 25);
		btnFinalizar.setEnabled(false);
		btnFinalizar.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				processando = false;
				addOrRemoveEspera = false;
				threadFilaProcessos.stop();
				threadProcessamento.stop();
				
				for (Thread t : threads) {
					t.stop();
				}
				
				estadoConfiguracao(true);
				
				clearTables();
				clearProcessamentoAtual();
				
				idx = 1;

				enableButtons(BTN_FINALIZAR);
			}
		});
		
		pnlConfiguracao.add(btnFinalizar);
		
		JLabel lblQuantum = new JLabel("Quantum:");
		lblQuantum.setBounds(10, 20, 90, 20);
		pnlConfiguracao.add(lblQuantum);
		
		tfQuantum = new JTextField();
		tfQuantum.setBounds(130, 20, 80, 20);
		pnlConfiguracao.add(tfQuantum);
		
		JLabel lblProcessoMinuto = new JLabel("Processos/Minuto:");
		lblProcessoMinuto.setBounds(220, 20, 120, 20);
		pnlConfiguracao.add(lblProcessoMinuto);
		
		tfQtdProcessosMinuto = new JTextField();
		tfQtdProcessosMinuto.setBounds(345, 20, 80, 20);
		pnlConfiguracao.add(tfQtdProcessosMinuto);
		
		JLabel lblMinTempoVida = new JLabel("Tempo vida (mín):");
		lblMinTempoVida.setBounds(10, 50, 120, 20);
		pnlConfiguracao.add(lblMinTempoVida);
		
		tfMinTempoVida = new JTextField();
		tfMinTempoVida.setBounds(130, 50, 80, 20);
		pnlConfiguracao.add(tfMinTempoVida);
		
		JLabel lblMaxTempoVida = new JLabel("Tempo vida (max):");
		lblMaxTempoVida.setBounds(220, 50, 120, 20);
		pnlConfiguracao.add(lblMaxTempoVida);
		
		tfMaxTempoVida = new JTextField();
		tfMaxTempoVida.setBounds(345, 50, 80, 20);
		pnlConfiguracao.add(tfMaxTempoVida);
		
		JLabel lblIoBound = new JLabel("I/O Bound (%):");
		lblIoBound.setBounds(10, 80, 120, 20);
		pnlConfiguracao.add(lblIoBound);
		
		tfPercentualIoBound = new JTextField();
		tfPercentualIoBound.setBounds(130, 80, 80, 20);
		pnlConfiguracao.add(tfPercentualIoBound);
		
		JLabel lblMinTempoProcessamentoIoBound = new JLabel("Tempo I/O (mín):");
		lblMinTempoProcessamentoIoBound.setBounds(220, 80, 120, 20);
		pnlConfiguracao.add(lblMinTempoProcessamentoIoBound);
		
		tfMinTempoProcessamentoIoBound = new JTextField();
		tfMinTempoProcessamentoIoBound.setBounds(345, 80, 80, 20);
		pnlConfiguracao.add(tfMinTempoProcessamentoIoBound);
		
		JLabel lblMinTempoEspera = new JLabel("Tempo espera (mín):");
		lblMinTempoEspera.setBounds(10, 110, 120, 20);
		pnlConfiguracao.add(lblMinTempoEspera);
		
		tfMinTempoEspera = new JTextField();
		tfMinTempoEspera.setBounds(130, 110, 80, 20);
		pnlConfiguracao.add(tfMinTempoEspera);
		
		JLabel lblMaxTempoEspera = new JLabel("Tempo espera (max):");
		lblMaxTempoEspera.setBounds(220, 110, 130, 20);
		pnlConfiguracao.add(lblMaxTempoEspera);
		
		tfMaxTempoEspera = new JTextField();
		tfMaxTempoEspera.setBounds(345, 110, 80, 20);
		pnlConfiguracao.add(tfMaxTempoEspera);
		
		add(pnlConfiguracao);
		repaint();
	}

	/**
	 * Atribui às variáveis utilizadas no simulador, os valores recebidos na configuração do simulador.
	 */
	private void setValores() {
		QUANTUM = new Double(tfQuantum.getText().trim());
		MIN_TEMPO_VIDA = new Double(tfMinTempoVida.getText().trim());
		MAX_TEMPO_VIDA = new Double(tfMaxTempoVida.getText().trim());
		NRO_PROCESSOS_MINUTO = new Double(tfQtdProcessosMinuto.getText().trim());
		PROBABILIDADE_IO_BOUND = new Double(tfPercentualIoBound.getText().trim());
		MIN_TEMPO_ESPERA = new Double(tfMinTempoEspera.getText().trim());
		MAX_TEMPO_ESPERA = new Double(tfMaxTempoEspera.getText().trim());
	}
	
	/**
	 * Habilita/desabilita o bloco de configuração de acordo com o estado do simulador.
	 * @param b
	 */
	private void estadoConfiguracao(boolean b) {
		tfQuantum.setEditable(b);
		tfMinTempoVida.setEditable(b);
		tfMaxTempoVida.setEditable(b);
		tfQtdProcessosMinuto.setEditable(b);
		tfPercentualIoBound.setEditable(b);
		tfMinTempoProcessamentoIoBound.setEditable(b);
		tfMinTempoEspera.setEditable(b);
		tfMaxTempoEspera.setEditable(b);
	}
	
	/**
	 * Define se idx recebido refere-se à um processo I/O Bound, de acordo com a media recebida. 
	 * @param idx
	 * @param mediaIo
	 * @return
	 */
	private boolean defineIoBound(Integer idx, Double mediaIo) {
		boolean io = false;
		
		if ((idx <= mediaIo) && (idx + 1) > mediaIo) {
			io = true;
			idx = 1;
		}
		
		return io;
	}
	
	/**
	 * Define um tempo de espera para os processos que são I/O Bound. Gera randomicamente
	 * um valor entre os valores MIN_TEMPO_ESPERA e MAX_TEMPO_ESPERA.	
	 * @return
	 */
	private Double defineTempoIoBound() {
		return Double.valueOf(new DecimalFormat("0.00").format(MIN_TEMPO_ESPERA + ((MAX_TEMPO_ESPERA - MIN_TEMPO_ESPERA) * new Random().nextDouble())).replace(',', '.'));
	}
	
	/**
	 * Valida a configuração fornecida na execução do simulador.
	 * @return {@link ConfiguracaoException}, caso haja algum erro de conversão de dados.
	 */
	private ConfiguracaoException validaConfiguracao() {
		ConfiguracaoException exception = new ConfiguracaoException();
		
		try {
			QUANTUM = new Double(tfQuantum.getText());
			NRO_PROCESSOS_MINUTO = new Double(tfQtdProcessosMinuto.getText());
			MAX_TEMPO_VIDA = new Double(tfMaxTempoVida.getText());
			MIN_TEMPO_VIDA = new Double(tfMinTempoVida.getText());
			PROBABILIDADE_IO_BOUND = new Double(tfPercentualIoBound.getText());
			
			exception.setResultado(true);
		} catch (Exception e) {
			exception.setResultado(false);			
			exception.setMensagem("Verifique os parâmetros fornecidos na configuração do procedimento !");
		}
		
		return exception;
	}
	
	/**
	 * Habilita/desabilita o botão recebido como parâmetro.
	 * @param button
	 */
	private void enableButtons(String button) {
		if (BTN_INICIAR.equals(button)) {
			btnIniciar.setEnabled(false);
			btnFinalizar.setEnabled(true);
		} else if (BTN_FINALIZAR.equals(button)) {
			btnIniciar.setEnabled(true);
			btnFinalizar.setEnabled(false);
		}
	}
	
	/**
	 * Limpa os registros das tabelas utilizadas no simulador.
	 */
	private void clearTables() {
		processos.clear();
		processosEspera.clear();
		processosHistorico.clear();
		
		processoTableModel.fireTableDataChanged();
		processoHistoricoTableModel.fireTableDataChanged();
		processoEsperaTableModel.fireTableDataChanged();
	}
	
	/**
	 * Limpa os campos do bloco do processamento atual.
	 */
	private void clearProcessamentoAtual() {
		tfIdProcesso.setText(null);
		tfNomeProcesso.setText(null);
		tfTempoVida.setText(null);
		tfTempoProcessado.setText(null);
		tfIoBound.setText(null);
		tfTentativa.setText(null);
	}
	
	/**
	 * Gera um valor randomico entre os valores mínimo e máximo recebidos.
	 * @param minValue
	 * @param maxValue
	 * @return
	 */
	private static Double generateRandonTime(Double minValue, Double maxValue) {
		Double tempoVida = null;
		tempoVida = minValue + ((maxValue - minValue) * new Random().nextDouble());
		tempoVida = Double.valueOf(new DecimalFormat("0.00").format(tempoVida).replace(',', '.'));
		return tempoVida;
	}
	
	/**
	 * Define os componentes utilizados no bloco de fila de processos.
	 */
	@SuppressWarnings("unchecked")
	private void initFilaProcessos() {
		processos = new LinkedList<Processo>();
		processoTableModel = new ProcessoTableModel((List<Processo>) processos);
		processoTableModel.addTableModelListener(new TableModelListener() {
			
			@Override
			public void tableChanged(TableModelEvent e) {
				if (!processando) {
					if (!(processos.isEmpty())) {
						processar(processos.poll());
					}
				}
			}
		});
		
		table = new JTable(processoTableModel);
		table.getColumnModel().getColumn(0).setPreferredWidth(55);
		table.getColumnModel().getColumn(1).setPreferredWidth(110);
		table.getColumnModel().getColumn(2).setPreferredWidth(60);
		table.getColumnModel().getColumn(3).setPreferredWidth(90);
		table.getColumnModel().getColumn(4).setPreferredWidth(90);
		table.getColumnModel().getColumn(5).setPreferredWidth(30);
		
		JPanel pnlProcessos = new JPanel();
		pnlProcessos.setBorder(BorderFactory.createTitledBorder("Fila de Processos"));
		pnlProcessos.setBounds(10, 180, 435, 380);
		
		JScrollPane scpFilaProcessos = new JScrollPane(table);
		scpFilaProcessos.setBounds(10, 20, 415, 350);
		pnlProcessos.add(scpFilaProcessos);
		
		add(pnlProcessos);
		
		repaint();
	}
	
	/**
	 * Define os componentes utilizados no bloco de fila de espera dos processos.
	 */
	@SuppressWarnings("unchecked")
	private void initFilaEspera() {
		processosEspera = new LinkedList<Processo>();
		processoEsperaTableModel = new ProcessoEsperaTableModel((List<Processo>) processosEspera);
		processoEsperaTableModel.addTableModelListener(new TableModelListener() {
			
			@Override
			public void tableChanged(TableModelEvent e) {
				if (addOrRemoveEspera) {
					if (!(processosEspera.isEmpty())) {
						Processo p = ((List<Processo>) processosEspera).get(processosEspera.size() -1);
						processarEspera(p);
					}
				}
			}
		});
		
		tableEspera = new JTable(processoEsperaTableModel);
		tableEspera.getColumnModel().getColumn(0).setPreferredWidth(55);
		tableEspera.getColumnModel().getColumn(1).setPreferredWidth(180);
		tableEspera.getColumnModel().getColumn(2).setPreferredWidth(100);
		tableEspera.getColumnModel().getColumn(3).setPreferredWidth(100);
		
		JPanel pnlProcessosEspera = new JPanel();
		pnlProcessosEspera.setBorder(BorderFactory.createTitledBorder("Espera"));
		pnlProcessosEspera.setBounds(450, 180, 435, 150);
		
		JScrollPane scpFilaProcessosEspera = new JScrollPane(tableEspera);
		scpFilaProcessosEspera.setBounds(10, 20, 415, 120);
		pnlProcessosEspera.add(scpFilaProcessosEspera);
		
		add(pnlProcessosEspera);
		repaint();
	}
	
	/**
	 * Define os componentes utilizados no bloco de histórico de processos já processados.
	 */
	private void initFilaHistorico() {
		processosHistorico = new LinkedList<Processo>();
		processoHistoricoTableModel = new ProcessoHistoricoTableModel((List<Processo>) processosHistorico);
		
		tableHistorico = new JTable(processoHistoricoTableModel);
		tableHistorico.getColumnModel().getColumn(0).setPreferredWidth(55);
		tableHistorico.getColumnModel().getColumn(1).setPreferredWidth(130);
		tableHistorico.getColumnModel().getColumn(2).setPreferredWidth(60);
		tableHistorico.getColumnModel().getColumn(3).setPreferredWidth(90);
		tableHistorico.getColumnModel().getColumn(4).setPreferredWidth(100);
		
		JPanel pnlProcessosHistorico = new JPanel();
		pnlProcessosHistorico.setBorder(BorderFactory.createTitledBorder("Histórico de Processos"));
		pnlProcessosHistorico.setBounds(450, 330, 435, 230);
		
		JScrollPane scpFilaProcessosHistorico = new JScrollPane(tableHistorico);
		scpFilaProcessosHistorico.setBounds(10, 20, 415, 200);
		pnlProcessosHistorico.add(scpFilaProcessosHistorico);
		
		add(pnlProcessosHistorico);
		
		repaint();
	}
	
	/**
	 * Ação para que seja realizada a simulação da execução dos processos na fila de processos. 
	 * @param processo
	 */
	private void processar(final Processo processo) {
		
		threadProcessamento = new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					processando = true;
					processoTableModel.fireTableDataChanged();

//					Processo processo = processos.poll();
//					processoTableModel.fireTableDataChanged();
					
					Double time = getTempoProcessamento(processo);
					
					atualizaProcessoAtual(processo);
					
					TimeUnit.MILLISECONDS.sleep(new Double(time * 1000).longValue());
					
					processo.setTempoProcessado(processo.getTempoProcessado() + time);
					
					if (processo.getTempoProcessado() < processo.getTempoVida()) {
						processo.setTentativa(processo.getTentativa() + 1);
						
						if (processo.isIoBound()) {
							processo.setStatus(StatusProcessoEnum.ESPERA);
							addOrRemoveEspera = true;
							
							processosEspera.offer(processo);
							processoEsperaTableModel.fireTableDataChanged();
							
						} else {
							processo.setStatus(StatusProcessoEnum.AGUARDANDO);
							processos.offer(processo);
							processoTableModel.fireTableDataChanged();
						}
					} else {
						processosHistorico.push(processo);
						processoHistoricoTableModel.fireTableDataChanged();
					}
					
					if (!(processos.isEmpty())) {
						processar(processos.poll());
					} else {
						processando = false;
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		
		threadProcessamento.start();
	}
	
	/**
	 * Ação para que seja realizada a simulação da espera do processo na fila de espera. 
	 * @param processo
	 */
	private void processarEspera(final Processo processo) {
		
		Thread t = new Thread(new Runnable() {
			
			@Override
			public void run() {
				
				try {
					TimeUnit.SECONDS.sleep(processo.getTempoEspera().longValue());
					
					addOrRemoveEspera = false;
					processosEspera.remove(processo);
					processoEsperaTableModel.fireTableDataChanged();
					
					processo.setStatus(StatusProcessoEnum.PRONTO);
					processos.offer(processo);
					processoTableModel.fireTableDataChanged();
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					Thread.currentThread().interrupt();
				}
			}
		});
		
		threads.add(t);
		
		t.start();
	}
	
	/**
	 * Define o tempo de processamento do processo em execução.
	 * Leva em consideração o tempo a ser processado do processo.
	 * Caso seja um processo I/O Bound, gera um tempo randomico de processamento entre os valores mínimo e máximo.
	 * Caso não seja processo I/O Bound e o tempo restante a ser processado seja maior que o valor do quantum, retorna o valor do quantum.
	 * @param processo
	 * @return
	 */
	private Double getTempoProcessamento(final Processo processo) {
		Double tempoRestante = processo.getTempoVida() - processo.getTempoProcessado();
		
		if (processo.isIoBound()) {
			Double tempoProcessamentoIo = generateRandonTime(MIN_TEMPO_PROCESSAMENTO_IO, QUANTUM);
			
			if (tempoProcessamentoIo < tempoRestante) {
				tempoRestante = tempoProcessamentoIo;
			}
		}
		
		return tempoRestante <= QUANTUM ? tempoRestante : QUANTUM;
	}
	
	/**
	 * Atualiza o processo em execução para que os atributos do mesmo sejam exibidos no bloco.
	 * @param processo
	 */
	private void atualizaProcessoAtual(Processo processo) {
		tfIdProcesso.setText(processo.getId().toString());
		tfNomeProcesso.setText(processo.getNome().toString());
		tfTempoVida.setText(processo.getTempoVida().toString());
		tfTempoProcessado.setText(processo.getTempoProcessado().toString());
		tfTentativa.setText(processo.getTentativa().toString());
		tfIoBound.setText(processo.isIoBound() ? "Sim" : "Não");
	}
	
}
